/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <dlfcn.h>
#include "pmem.h"
#include "common.h"
#include "state.h"
#include "reg.h"

enum{ DIFFTEST_TO_DUT, DIFFTEST_TO_REF, };
typedef struct {
				word_t gpr[32];
				paddr_t pc;
} CPU_state;
typedef void(*MEMCPY)(paddr_t addr, void *buf, size_t n, bool direction);
MEMCPY ref_difftest_memcpy = NULL;
typedef void(*REGCPY)(void *dut, bool direction);
REGCPY ref_difftest_regcpy = NULL;
typedef void(*EXEC)(uint64_t n);
EXEC ref_difftest_exec = NULL;
typedef void(*RAISE_INTR)(uint64_t NO);
RAISE_INTR ref_difftest_raise_intr= NULL;
typedef void (*INIT)(int port);
INIT ref_difftest_init = NULL;
typedef void(*REGCPY1)(void *dut);
REGCPY1 ref_difftest_regcpy_last_toref = NULL;
typedef void(*CSRCPY)(uint64_t *start);
CSRCPY ref_difftest_csrscpy = NULL;
typedef void(*INTRCPY)(void);
INTRCPY ref_difftest_intrcpy = NULL;

#ifdef CONFIG_DIFFTEST

static int is_skip_ref = 0;
static int skip_dut_nr_inst = 0;

// this is used to let ref skip instructions which
// can not produce consistent behavior with NEMU
void difftest_skip_ref() {
  is_skip_ref = 2;
  // If such an instruction is one of the instruction packing in QEMU
  // (see below), we end the process of catching up with QEMU's pc to
  // keep the consistent behavior in our best.
  // Note that this is still not perfect: if the packed instructions
  // already write some memory, and the incoming instruction in NEMU
  // will load that memory, we will encounter false negative. But such
  // situation is infrequent.
  skip_dut_nr_inst = 0;
}

// this is used to deal with instruction packing in QEMU.
// Sometimes letting QEMU step once will execute multiple instructions.
// We should skip checking until NEMU's pc catches up with QEMU's pc.
// The semantic is
//   Let REF run `nr_ref` instructions first.
//   We expect that DUT will catch up with REF within `nr_dut` instructions.
void difftest_skip_dut(int nr_ref, int nr_dut) {
  skip_dut_nr_inst += nr_dut;

  while (nr_ref -- > 0) {
    ref_difftest_exec(1);
  }
}

void init_difftest(char *ref_so_file, long img_size, int port) {
  assert(ref_so_file != NULL);

  void *handle;
  handle = dlopen(ref_so_file, RTLD_LAZY);
  assert(handle);

  ref_difftest_memcpy = (MEMCPY)dlsym(handle, "difftest_memcpy");
  assert(ref_difftest_memcpy);

  ref_difftest_regcpy = (REGCPY)dlsym(handle, "difftest_regcpy");
  assert(ref_difftest_regcpy);

  ref_difftest_exec = (EXEC)dlsym(handle, "difftest_exec");
  assert(ref_difftest_exec);

  ref_difftest_raise_intr =(RAISE_INTR)dlsym(handle, "difftest_raise_intr");
  assert(ref_difftest_raise_intr);

  void (*ref_difftest_init)(int) = (INIT)dlsym(handle, "difftest_init");
 	assert(ref_difftest_init);

  ref_difftest_regcpy_last_toref = (REGCPY1)dlsym(handle, "difftest_regcpy_last_toref");
  assert(ref_difftest_regcpy_last_toref);

  ref_difftest_csrscpy = (CSRCPY)dlsym(handle, "difftest_csrscpy");
  assert(ref_difftest_csrscpy);

  ref_difftest_intrcpy = (INTRCPY)dlsym(handle, "difftest_intrcpy");
  assert(ref_difftest_intrcpy);

  Log("Differential testing: %s", ANSI_FMT("ON", ANSI_FG_GREEN));
  Log("The result of every instruction will be compared with %s. "
      "This will help you a lot for debugging, but also significantly reduce the performance. "
      "If it is not necessary, you can turn it off in menuconfig.", ref_so_file);
  ref_difftest_init(port);

  ref_difftest_memcpy(RESET_VECTOR,guest_to_host(RESET_VECTOR), img_size, DIFFTEST_TO_REF);
  //ref_difftest_regcpy(cpu_gpr, DIFFTEST_TO_REF);
}
void difftest_cp_intr(){
	ref_difftest_intrcpy();
}
extern uint64_t *csrs_pt;
void difftest_cp_csrs(){
		ref_difftest_csrscpy(csrs_pt);
}
bool isa_difftest_checkregs(CPU_state *ref,paddr_t pc)
{
	for(int i = 0; i<32 ; i++){
			if(ref->gpr[i] != gpr(i)) {
				printf("ref_pc:%lx,dut_pc:%lx\n",ref->pc,pc);
				for(int j=0;j<32;j++){
				printf("ref[%d]:%lx\t,dut[%d]:%lx\n",j,ref->gpr[j],j,gpr(j));
				}
				return false;
			}
	}
	if(ref->pc != pc){
				printf("ref_pc:%lx,dut_pc:%lx\n",ref->pc,pc);
				return false;
	}
	return true;
}
static void checkregs(CPU_state *ref, paddr_t pc) {
  if (!isa_difftest_checkregs(ref, pc)) {
    npc_state.state = NPC_ABORT;
    npc_state.halt_pc = pc;
    reg_display();
  }
}

void difftest_cpyregs(){
    ref_difftest_regcpy(cpu_gpr, DIFFTEST_TO_REF);
}
void difftest_step(paddr_t pc,paddr_t npc) {
  CPU_state ref_r;

  if (skip_dut_nr_inst > 0) {
		//printf("%lx\n",cpu_gpr[32]);
    ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);
    if (ref_r.pc == npc) {
      skip_dut_nr_inst = 0;
      checkregs(&ref_r, npc);
      return;
    }
    skip_dut_nr_inst --;
    if (skip_dut_nr_inst == 0)
						assert(0);
    return;
  }

  if (is_skip_ref>1){
		is_skip_ref --;
		ref_difftest_regcpy_last_toref(cpu_gpr); 
		ref_difftest_csrscpy(csrs_pt);
		return;
	}
	else if(is_skip_ref == 1){
    ref_difftest_regcpy(cpu_gpr, DIFFTEST_TO_REF);
    is_skip_ref = 0;
    return;
  }

  ref_difftest_exec(1);
		//printf("%lx\n",cpu_gpr[32]);
  ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);

  checkregs(&ref_r, pc);
}
#else
void init_difftest(char *ref_so_file, long img_size, int port) { }
#endif
